#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <sys/epoll.h>
#include <fcntl.h>


//使用epoll实现多路复用


//将文件描述符设置为非阻塞
void SetNoBlock(int fd)
{
  int flag =  fcntl(fd,F_GETFL);
  fcntl(fd,F_SETFL, flag | O_NONBLOCK);
}
//启动服务器
int server_start(const char * ip,const short port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if(sock < 0)
  {
    perror("socket");
    return -1;
  }
  
  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(ip);
  addr.sin_port = htons(port);

  int ret = bind(sock,(sockaddr *)&addr,sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    return -1; 
  }

  ret = listen(sock,5);
  if(ret < 0)
  {
    perror("listen");
    return -1;
  }
  return sock;
}


//处理连接socket就绪
//封装处理非阻塞轮询accept()
void Process_listen_socket(int epfd,int listen_socket,sockaddr_in  * peer,socklen_t peer_len)
{
  //就进行非阻塞式轮询accept()
  
  //我们看到这里返回的events数据中并没有文件描述符
  //所以我们最开始将data.fd赋值为文件描述符就是在这里用到
  while(1)
  {
    int new_socket = accept(listen_socket,(sockaddr *)peer,&peer_len);

    if(new_socket < 0 && errno ==EAGAIN)
    {
      //因为将文件描述符都设置为非阻塞，这里的accpt()为非阻塞的
      //这里就需要轮询式的进行accpet()
      //这里的listen()函数的第二个参数为5，表示排队等待连接的客户端最多有5个
      //说明已经将所有的文件描述符进行accpet
      perror("accpet");
      return;
    }
    //如果创建new_socket成功之后
    //就将new_socket 加入到epfd中，让epoll_wait()再去监视new_socket的状态

    //将文件描述符设置为非阻塞
    SetNoBlock(new_socket);

    epoll_event event ;
    event.data.fd = new_socket;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd,EPOLL_CTL_ADD,new_socket,&event); 

  }
}


//封装Read,实现非阻塞的轮询式的read()
ssize_t Read(int sock,char * buf,ssize_t max_size)
{
  if(buf == NULL || max_size <= 0)
  {
    return -1;
  }
  ssize_t total_size = 0;
  while(total_size < max_size)
  {
    //控制每次读取都不能使buf越界
    //剩余空间如果大于1024，就一次性读取1024，否则就读取剩余的空间大小
    int len = (max_size - total_size) > 1024 ? 1024 :(max_size - total_size);
    ssize_t read_size = read(sock,buf+total_size,len);
    if(read_size < 0 && errno == EAGAIN)
    {
      //这里的read()为非阻塞的
      //说明为缓冲区中没有数据资源
      //非阻塞轮询就结束了
      printf("data not ready\n");
      break;
    }

    //如果这里为读取失败，read_size < 0 errno != EAGAIN
    //让其再次尝试读取
    
    if(read_size == 0)
    {
      printf("rad done\n");
      break;
    }
    else
    {
      //正常读取的情况
      //修改total_size
      total_size += read_size; 
    }
  }
  return total_size;
}


//处理连接上socket,进行读取数据
void Process_accept_socket(int epfd, int acc_socket,sockaddr_in * peer)
{
  char buf[1024 * 10] = {0};
  //非阻塞轮询进行读取
  ssize_t read_size = Read(acc_socket,buf,sizeof(buf)-1); 

  if(read_size <= 0 )
  {
    close(acc_socket);
    epoll_ctl(epfd,EPOLL_CTL_DEL,acc_socket,NULL);
    printf("client[%s:%d] disconnect!\n",inet_ntoa(peer->sin_addr),peer->sin_port);
    return;
  }
  else
  {
    //正常读取的情况
    buf[read_size] = '\0';
    printf("[client %s:%d.%d]say:%s",inet_ntoa(peer->sin_addr),ntohs(peer->sin_port),acc_socket,buf);
    //回显服务
    //将收到的内容发送给服务器
    write(acc_socket,buf,strlen(buf));
  }
}

//主函数
int main(int argc,char * argv[])
{
  //检验命令行参数是否正确
  if(argc != 3)
  {
    printf("Usage : ./server ip port\n");
    return 1;
  }

  //一、启动服务器
  int listen_socket = server_start(argv[1],atoi(argv[2]));

  if(listen_socket < 0)
  {
    printf("server start failed\n");
    return 2;
  }
  //将文件描述符设置为非阻塞
  SetNoBlock(listen_socket);

  printf("server start ok\n");

  //1.创建epoll对象
  int epfd = epoll_create(256);
  if(epfd < 0)
  {
    perror("epoll_create");
    return 3;
  }
  epoll_event event;
  event.data.fd = listen_socket;//此处将要监听的文件描述符放在data中，后面取值要用到
  //event.events = EPOLLIN;//关心的事件为读事件
  event.events = EPOLLIN | EPOLLET;//关心的事件为读，并且设置为边缘触发
  //2.将listen_socket添加到epfd中
  epoll_ctl(epfd,EPOLL_CTL_ADD,listen_socket,&event);


  //二、进行事件循环
  while(true)
  {
    //创建事件数组，返回已经就绪的文件
    epoll_event events[2];
    int size = epoll_wait(epfd,events,sizeof(events)/sizeof(events[0]),-1);
    if(size < 0)
    {
      perror("epoll_wait");
      continue;
    }

    if(size == 0)
    {
      printf("time out\n");
      continue;
    }

    //epoll_wait()成功返回后
    int i = 0;
    for(i = 0; i < size ;++i)
    {
      sockaddr_in peer;
      socklen_t peer_len =  sizeof(peer);
      if(events[i].data.fd == listen_socket)
      {
      //(a)listen_socket就绪
      //连接事件就绪
        Process_listen_socket(epfd,listen_socket,&peer,peer_len);
        continue;
      }//end if(events[i].data.fd == listen_socket)
      else
      {
     //(b)new_sock就绪
     //读事件就绪
        Process_accept_socket(epfd,events[i].data.fd,&peer);
      }//end else
    }//end for()
  }//end while(true)

  close(epfd);
  close(listen_socket);

}// end main
